import { AssetManager, assetManager, Canvas, Component, director, game, Node, resources, Size, Sprite, sys, UITransform, v2, v3, v4, Vec2, Vec3, Vec4, view, warn } from "cc";

// 检测全局命名空间是否存在
const __Global__ = window ?? globalThis;
if(!Reflect.has(__Global__, 'mtec')){
	Reflect.set(__Global__, 'mtec', {});
	Reflect.set(mtec, '__mtec__', 'Many technologies');
}

// 初始化子命名空间
['sp', 'size', 'vector', 'cc']
.forEach(space_name=>{
	if(!Reflect.has(mtec, space_name)){
		Reflect.set(mtec, space_name, {});
		Reflect.set(Reflect.get(mtec, space_name), '__space_description__', 'This is a mtec subnamespace for ' + space_name);
	}
});

Reflect.defineProperty(mtec.cc, 'initTime', {
	get() {
		return Reflect.get(game, '_initTime');
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.cc, 'totalTime', {
	get() {
		return game.totalTime;
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.cc, 'frameStartTime', {
	get() {
		return game.frameStartTime - mtec.cc.initTime;
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.cc, 'residueTime', {
	get() {
		return mtec.cc.frameStartTime+mtec.cc.frameTime-mtec.cc.totalTime;
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.cc, 'frameTime', {
	get() {
		return game.frameTime;
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.cc, 'is_long_screen', {
	get(){
		let [R, r] = [mtec.size.canvasSize, mtec.size.designSize].map(s=>s.width/s.height);
		return r > R;
	},
	enumerable: true,
	configurable: false,
});

if(typeof requestIdleCallback !== 'undefined'){
	mtec.cc.IdleCallback = function(call, args, complete, options){
		if(options){
			if(!Reflect.has(options, 'start_stamp')) options.start_stamp = performance.now();
			if(!Reflect.has(options, 'timeout')) options.timeout = 100;
		}else options = {start_stamp: performance.now(), timeout: 100};

		requestIdleCallback(()=>{
			if(mtec.cc.residueTime>0 || performance.now()-options.start_stamp >= options.timeout){
				Promise.resolve(call(...args)).then(r=> typeof complete == 'function' ? complete(r, args) : void 0);
			}else mtec.cc.IdleCallback(call, args, complete, options);
		});
	}
}else if(typeof requestAnimationFrame !== 'undefined'){
	mtec.cc.IdleCallback = function(call, args, complete, options){
		if(options){
			if(!Reflect.has(options, 'start_stamp')) options.start_stamp = performance.now();
			if(!Reflect.has(options, 'timeout')) options.timeout = 100;
		}else options = {start_stamp: performance.now(), timeout: 100};
		requestAnimationFrame(()=>{
			if(mtec.cc.residueTime>0 || performance.now()-options.start_stamp >= options.timeout){
				Promise.resolve(call(...args)).then(r=> typeof complete == 'function' ? complete(r, args) : void 0);
			}else mtec.cc.IdleCallback(call, args, complete, options);
		});
	}
}else{
	mtec.cc.IdleCallback = function(call, args, complete, options){
		if(options){
			if(!Reflect.has(options, 'start_stamp')) options.start_stamp = performance.now();
			if(!Reflect.has(options, 'timeout')) options.timeout = 100;
		}else options = {start_stamp: performance.now(), timeout: 100};
		setTimeout(()=>{
			if(mtec.cc.residueTime>0 || performance.now()-options.start_stamp >= options.timeout){
				Promise.resolve(call(...args)).then(r=> typeof complete == 'function' ? complete(r, args) : void 0);
			}else mtec.cc.IdleCallback(call, args, complete, options);
		}, 0);
	}
}

mtec.cc.frameWhile = function(list, call){
	if(!list || !Array.isArray(list) || list.length<=0) return Promise.resolve(0);
	let np = new mtec.NudityPromise<0>();
	list.forEach((el, i)=> mtec.cc.IdleCallback(call, [el, i, list], ()=>(i==list.length-1) ? np.resolve(0) : void 0) );
	return np.promise;
}

mtec.cc.loadBundle = function(bundle_name){
	return new Promise<AssetManager.Bundle>((s, j)=>{
		assetManager.loadBundle(bundle_name, null, (err, bundle)=>{
			if(err) s(undefined);
			else if(bundle) s(bundle);
			else s(undefined);
		});
	});
};

mtec.cc.loadRes = function(path, type, bundle, call){
	//@ts-ignore
	[bundle, call] = mtec.pickValueByType([bundle, call], [['object', resources], ['function', undefined]]);

	let asset = bundle.get(path, type);
	if(!asset){
		bundle.load(path, type, (err, a)=>{
			if(a) call && call(a);
			else warn('资源加载失败：', path, type, err);
		});
	}else if(call) call(asset);
}

mtec.cc.loadResAsync = function(path, type, bundle){
	bundle = bundle??resources;

	return new Promise((s, j)=>{
		let asset = bundle.get(path, type);
		if(asset) s(asset);
		else{
			bundle.load(path, type, (err, a)=>{
				if(a) s(a);
				else warn('资源加载失败：', path, type, err);
			});

		}
	});
}

mtec.cc.creatEventHandle = function(options){
	return Object.assign(new Component.EventHandler(), options);
}

mtec.cc.VecAssist = class implements mtec.cc.VecAssist{
	private inner: UITransform;
	private container: UITransform;
	private __vec__: Vec3;

	constructor(node: Node, ref: Node){
		if (!node.parent){
			mtec.log.tag(`node<${node.name}> 不在场景中，无法使用坐标辅助类的功能!: red`);
			return void 0;
		}

		ref = ref&&ref.isValid ? ref : node.parent;

		[this.inner, this.container] = [node, ref].map(n=>n.getComponent(UITransform));
		this.__vec__ = v3(node.position);
	}

	private get container_upper(){
		return this.container.height * (1 - this.container.anchorY);
	}

	private get container_bottom(){
		return this.container.height * this.container.anchorY;
	}

	private get container_left(){
		return this.container.width * this.container.anchorX;
	}

	private get container_right(){
		return this.container.width * (1 - this.container.anchorX);
	}

	private get inner_upper(){
		return this.inner.height * (1 - this.inner.anchorY);
	}

	private get inner_bottom(){
		return this.inner.height * this.inner.anchorY;
	}

	private get inner_left(){
		return this.inner.width * this.inner.anchorX;
	}

	private get inner_right(){
		return this.inner.width * (1 - this.inner.anchorX);
	}

	public get top(){
		return this.container_upper - this.inner_upper;
	}

	public get bottom(){
		return this.inner_bottom - this.container_bottom;
	}

	public get left(){
		return this.inner_left - this.container_left;
	}

	public get right(){
		return this.container_right - this.inner_right;
	}

	public get over_top(){
		return this.container_upper + this.inner_bottom;
	}

	public get over_bottom(){
		return -this.container_bottom - this.inner_upper;
	}

	public get over_left(){
		return -this.container_left - this.inner_right;
	}

	public get over_right(){
		return this.container_right + this.inner_left;
	}

	public get center(){
		let x = (0.5 - this.container.anchorX) * this.container.width + (this.inner.anchorX - 0.5) * this.inner.width;
		let y = (0.5 - this.container.anchorY) * this.container.height + (this.inner.anchorY - 0.5) * this.inner.height;

		return v3(x, y, this.inner.node.position.z);
	}

	public get vec(){
		return this.__vec__;
	}

	public getPosition(dx: 'center'|`${''|'over_'}${'left'|'right'}`, dy?:'center'|`${''|'over_'}${'top'|'bottom'}`){
		dy = dy ?? 'center';

		this.vec.set(
			dx=='center' ? this.center.x : Reflect.get(this, dx),
			dy=='center' ? this.center.y : Reflect.get(this, dy),
			this.inner.node.position.z
		);

		return this.vec;
	}

	public static getPosition<Dx extends 'center'|`${''|'over_'}${'left'|'right'}`, Dy extends 'center'|`${''|'over_'}${'top'|'bottom'}`>
	(node: Node, ...residue: ['center']|[Dx, Dy]|[Node, 'center']|[Node, Dx, Dy]): Vec3{
		let ref: Node; let dx: Dx; let dy: Dy;
		if(residue[0] instanceof Node){
			[ref, dx, dy] = residue as [Node, Dx, Dy];
		}else{
			ref = node.parent;
			[dx, dy] = residue as [Dx, Dy];
		}

		return new mtec.cc.VecAssist(node, ref).getPosition(dx, dy);
	}
}

mtec.cc.skinPeeler = function(node, sframe, inner){
	let uitrans = node.getComponent(UITransform);
	let node_size = uitrans.contentSize.clone();
	let sfrm_size = sframe.originalSize.clone();

	let sprite = node.getComponent(Sprite);
	if(!sprite) sprite = node.addComponent(Sprite);
	sprite.spriteFrame = sframe;

	let ratio: number;
	inner = inner ?? true;
	if(typeof inner=='boolean' || inner.width===inner.height){
		if(typeof inner!='boolean') inner = inner.width;
		ratio = Math[inner ? 'min' : 'max'](node_size.width / sfrm_size.width, node_size.height / sfrm_size.height);
	}else{
		let side = inner.width ? 'width' : 'height';
		ratio = node_size[side] / sfrm_size[side];
	}

	uitrans.setContentSize(sfrm_size.width * ratio, sfrm_size.height * ratio);
}

mtec.cc.getWorldPosition = function(node, out){
	if(!node || !node.isValid) return void 0;

	return node.getWorldPosition(out??v3());
}

mtec.cc.getLocalPosition = function(local: Node, node: Vec3|Node, out?: Vec3){
	if([local && local.isValid, node instanceof Vec3 ? true : (node && node.isValid)].includes(false)) return void 0;

	return local.getComponent(UITransform).convertToNodeSpaceAR(node instanceof Vec3 ? node : mtec.cc.getWorldPosition(node), out??v3());
};

mtec.cc.v2 = function(__x__, y){
	if(__x__ instanceof Vec2) return __x__;

	let x: number;
	if(typeof __x__ == 'number'){
		x = __x__;
		y = y??x;
	}else ({x, y} = __x__);

	return v2(x, y);
}

mtec.cc.v3 = function(__x__, y, z){
	if(__x__ instanceof Vec3) return __x__;

	let x: number;
	if(typeof __x__ == 'number'){
		x = __x__;
		y = y??x;
		z = z??y;
	}else if(__x__ instanceof Vec2){
		z = y??0;
		({x, y} = __x__);
	}else if(__x__ instanceof Vec4){
		({x, y, z} = __x__);
	}

	return v3(x, y, z);
}

mtec.cc.v4 = function(__x__, y, z, w){
	if(__x__ instanceof Vec4) return __x__;

	let x: number;
	if(typeof __x__  == 'number'){
		x = __x__;
		y = y??x;
		z = z??y;
		w = w??z;
	}else if(__x__ instanceof Vec2){
		w = z??0;
		z = y??0;
		({x, y} = __x__);
	}else if(__x__ instanceof Vec3){
		w = z??0;
		({x, y, z} = __x__);
	}

	return v4(x, y, z, w);
}

mtec.cc.adaptBackgroundNode = function(node, container){
	container = container ?? node.parent;
	if(!container || ![node, container].every(n=>n.isValid)) return void 0;

	let [ui_container, ui_node] = [container, node].map(n=>{
		let ui = n.getComponent(UITransform);
		if(!ui) ui = n.addComponent(UITransform);
		return ui;
	});

	let node_size: Size;
	if(node.getComponent(Sprite)) node_size = node.getComponent(Sprite).spriteFrame.originalSize;
	else node_size = ui_node.contentSize;

	let [R, r] = [ui_container, node_size].map($=>$.width / $.height);

	let ratio: number;
	if(R > r) ratio = ui_container.width / node_size.width;
	else ratio = ui_container.height / node_size.height;

	ui_node.setContentSize(node_size.width * ratio, node_size.height * ratio);
}

Reflect.defineProperty(mtec.cc, 'canvas', {
	get(){
		return director.getScene().getComponentInChildren(Canvas);
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.size, 'designSize', {
	get() {
		return view.getDesignResolutionSize();
	},
	enumerable: true,
	configurable: false,
});

Reflect.defineProperty(mtec.size, 'canvasSize', {
	get() {
		return mtec.cc.canvas.getComponent(UITransform).contentSize;
	},
	enumerable: true,
	configurable: false,
});

mtec.size.innerRatio = function(target, container){
	let [R, r] = [container, target].map(s=>s.width / s.height);
	/** @type {number} */
	let ratio: number;
	if(r > R) ratio = container.width / target.width;
	else ratio = container.height / target.height;
	return ratio;
}

mtec.size.exterRatio = function(target, filler){
	let [R, r] = [target, filler].map(s=>s.width / s.height);
	/** @type {number} */
	let ratio: number;
	if(R > r) ratio = filler.height / target.height;
	else ratio = filler.width / target.width;
	return ratio;
}

mtec.vector.distance = function(a, b){

	if([a, b].map(v=>v instanceof Vec2).includes(true)) return Vec2.subtract(v2(), mtec.cc.v2(a), mtec.cc.v2(b)).length();

	if([a, b].map(v=>v instanceof Vec3).includes(true)) return Vec3.subtract(v3(), mtec.cc.v3(a), mtec.cc.v3(b)).length();

	return Vec4.subtract(v4(), mtec.cc.v4(a), mtec.cc.v4(b)).length();
}